require 'socket'
require 'zdc.doc'
require 'zsvc.packet'
require 'zsvc.accept'
local monutil=require 'zsvc.monutil'

module(..., package.seeall)
local EvHandle = {}
local EvHandle_mt = {__metatable = {}, __index = EvHandle}

function EvHandle:_bv_init_connect(host,port)
	local react=self.react
	if not react.NewBV then
		return self:ConnectTo(host,port)
	end
	local fd=socket.tcp()
	fd:settimeout(0)
	self.logger:info('connect to ' .. tostring(host)..':'..tostring(port))
	local ret, err = fd:connect(host,port)
	if not ret and err ~= "timeout" then
		fd:close()
		self.logger:warn('Appcon connect error:' .. logging.tostring(ret),logging.tostring(err))
		--TODO timer redo
		return 
	else
		self.fd=fd
		self.connwait=self.timeout or 5
		self:_bv_init()
		return true
	end
end
function EvHandle:_bv_init()
	self.rd__timeout=self.rd__timeout or 60*60*24*7
	self.wr__timeout=self.wr__timeout or 60*60*24*7
	local react=self.react
	if not react.NewBV then
		self.__ev_recv=self:AddEvent(self:GetFD(),self.react.EV_READ,self.RecvCB,self.rd__timeout)
		return
	end
	self.__bv=react:NewBV(self.OnClose,self,self:GetFD(),self._bv_cb_read,self._bv_cb_write,self._bv_cb_error)
	self.__bv:set_timeouts(self.rd__timeout,self.wr__timeout)
	self.__bv:enable(react.EV_READ)
	self.__bv:set_read_watermarks(10, 10)
	self.__bv:set_write_watermarks(0, 1024)
end
function EvHandle:_bv_close()
	if self.__bv then
		--self.__bv:close()
		self.__bv=nil
	end
	if self.__ev_send then
		self.react:CloseCB(self.__ev_send)
		self.__ev_send=nil
	end
	if self.__ev_recv then
		self.react:CloseCB(self.__ev_recv)
		self.__ev_recv=nil
	end
end

function EvHandle:_bv_cb_read(bv)
	local bv=self.__bv
	local rbuff=bv:get_read()
	local rp=self.rp
	if not rp.header then
		rp:Attach(rbuff:get_data(10))
		local h=rp:ParseHeader()
		local len=10+h.size
		bv:set_read_watermarks(len, len)
		return
	else
		rp:Attach(rbuff:get_data())
		rbuff:drain(-1)
		bv:set_read_watermarks(10, 10)
		do
			--local pos,ch=rp:GetCH()
			local por,msg=rp:GetMsg()
			print((self.name or ''),'recv',logging.tostring(msg))
		end
		self:OnMsg(rp)
		rp:Clear()
		bv:set_read_watermarks(len, len)
	end
end

function EvHandle:_bv_cb_write(bv)
	local np=self.que:Get()
	if not np then
		return
	end
	local buf,pos=np:GetBufAndPos()
	local bv=self.__bv
	local rbuff=bv:get_write()
	rbuff:add(buf)
end

function EvHandle:_bv_cb_error(bv,what)
	self:OnClose()
end

function EvHandle:_bv_send(np)
	self.que:Put(np)
	self.__bv:enable(react.EV_READ+react.EV_WRITE)
end

function GetFD(self)
	return self.fd
end

function AddEvent(self,fd,event,cb,tm)
	return self.react:AddEventCB(self.OnClose,self,fd,event,cb,tm)
end

function SendPacket(self,np)
	self.que:Put(np)
	if not self.__ev_send then
		self.__ev_send=self:AddEvent(self:GetFD(),self.react.EV_WRITE,SendCB,self.wr_timeout)
	end
end

function ConnectTo(self,host,port)
	local fd=socket.tcp()
	fd:settimeout(0)
	self.logger:info('connect to ' .. tostring(host)..':'..tostring(port))
	local ret, err = fd:connect(host,port)
	if not ret and err ~= "timeout" then
		fd:close()
		self.logger:warn('Appcon connect error:' .. logging.tostring(ret),logging.tostring(err))
		--TODO timer redo
		return 
	else
		self.fd=fd
		self.connwait=self.timeout or 20
		self.__ev_send=self:AddEvent(self:GetFD(),self.react.EV_WRITE,self.ConnectCB,self.connwait)
		return true
	end
end
function reconnectcb(self,event)
	if event==self.react.EV_TIMEOUT then
		self.connwait=false
		self.logger:warn((self.name or '') .. ' connect timeout')
		self:OnClose()
		return -1
	end
end
function ConnectCB(self,event)
	if event==self.react.EV_TIMEOUT then
		self.connwait=false
		self.logger:warn((self.name or '') .. ' connect timeout')
		self:OnClose()
		return -1
	end
	local fd=self:GetFD()
	local r,err,err2=self.react.recvfd(fd,0)
	--print('ccb',r,err,err2)
	if not r and err~='timeout' then
		--print('adsf',err)
		self.connwait=false
		self.logger:warn((self.name or '') .. ' ' .. tostring(err))
		self.__ev_send=nil
		self.__ev_recv=nil
		self:AddEvent(nil,self.react.EV_TIMEOUT,reconnectcb,self.connwait or 20)		
		return -1
	end		
	--self.__ev_send=self:AddEvent(self:GetFD(),self.react.EV_WRITE,self.SendCB,self.wr__timeout)
	self.__ev_recv=self:AddEvent(self:GetFD(),self.react.EV_READ,self.RecvCB,self.rd__timeout)
	self.__ev_send=nil
	if self.connwait then
		self:cb_conn_ready()
	end
	return -1
end
function TrySend(self)
	local fd=self:GetFD()
	if not fd then return end
	for i=1,3 do
		local buf,pos
		if self.sp then
			buf,pos=self.sp:GetBufAndPos()
		end
		if not buf then
			self.sp=self.que:Get()
			if not self.sp then
				return i
			end
			buf,pos=self.sp:GetBufAndPos()
			if self.logger:chkLevel('DEBUG') then
				local pos,ch=self.sp:GetCH()
				self.logger:debug((self.name or '')..' send '..logging.tostring(ch))
			end
		end
		local npos,err
		npos,err,pos=self.react.sendfd(fd,buf,pos)
		if not npos and err ~= "timeout" and err ~= "wantwrite" then
			return 
		end
		npos=npos or pos
		self.sp:MoveToNext(npos+1)
	end
	return 3
end
function SendCB(self,event)
	if event==self.react.EV_TIMEOUT then
		self.logger:info((self.name or '') .. ' send timeout')
		self:OnClose()
		return -1
	end
	
	local fd=self:GetFD()
	if not fd then return -1 end
	local buf,pos
	if self.sp then
		buf,pos=self.sp:GetBufAndPos()
	end
	if not buf then
		self.sp=self.que:Get()
		if not self.sp then
			self.__ev_send=nil
			return -1
		end
		buf,pos=self.sp:GetBufAndPos()
		if self.logger:chkLevel('DEBUG') then
			local pos,ch=self.sp:GetCH()
			self.logger:debug((self.name or '')..' send ',logging.tostring(ch))
		end
	end
	local npos,err
	npos,err,pos=self.react.sendfd(fd,buf,pos)
	if not npos and err ~= "timeout" and err ~= "wantwrite" then
		self.logger:info((self.name or '') .. ' socket error ' .. tostring(err))
		self:OnClose()
		return -1
	end
	npos=npos or pos
	if npos then
		self.sp:MoveToNext(npos+1)
		
		--avoid next call write
		buf,pos=self.sp:GetBufAndPos()
		if not buf then
			self.sp=self.que:Get()
			if not self.sp then
				self.__ev_send=nil
				return -1
			end
		end
	end
	return self.react.EV_WRITE,self.wr__timeout
end

function RecvCB(self,event)
	if event==self.react.EV_TIMEOUT then
		self.logger:info((self.name or '') .. ' recv timeout')
		self:OnClose()
		return -1
	end
	local fd=self:GetFD()
	if not fd then return -1 end
	local rp=self.rp
	local r,err,_r
	local rnum=self.react.EdgeTriger or 1
	repeat
		if #rp.buf < 10 then
			r,err,_r=self.react.recvfd(fd,10-#rp.buf)
			if not r and err ~= "timeout" and err ~= "wantread" then
				if self.connwait then
					self.logger:warn(self.name .. ' connect faild')
					--return
				end
				self.logger:info((self.name or '') .. ' socket error ' .. tostring(err))
				self:OnClose()
				return -1
			end
			r= r or _r
			if not r then break end
			rp.buf=rp.buf .. r
			if #rp.buf < 10 then
				break
			end
		end
		local h=rp:ParseHeader()
		if h.size>#rp.buf-10 then
			r,err,_r=self.react.recvfd(fd,h.size+10-#rp.buf)
			if not r and err ~= "timeout" and err ~= "wantread" then
				self.logger:info((self.name or '') .. ' socket error ' .. tostring(err))
				self:OnClose()
				return -1
			end
			r= r or _r
			if not r then break end
			rp.buf=rp.buf .. r
			if h.size>#rp.buf-10 then
				break
			end
		end
	--[[
		do
			--local pos,ch=rp:GetCH()
			local por,msg=rp:GetMsg()
			print((self.name or ''),'recv',logging.tostring(msg))
		end
	--]]
		self:OnMsg(rp)
		rp:Clear()
		rnum=rnum-1
	until rnum<=0
	return self.react.EV_READ,self.rd__timeout
end

function InitEvent(evh)
	for k,v in pairs(EvHandle) do
		evh[k]=v
	end
	evh.GetFD=GetFD
	evh.AddEvent=AddEvent
	evh.SendPacket=SendPacket
	evh.ConnectTo=ConnectTo
	evh.ConnectCB=ConnectCB
	evh.TrySend=TrySend
	evh.SendCB=SendCB
	evh.RecvCB=RecvCB
end